﻿#region File and License Information
/*
<File>
	<License Type="BSD">
		Copyright © 2009 - 2012, Outcoder. All rights reserved.
	
		This file is part of Calcium (http://calciumsdk.net).

		Redistribution and use in source and binary forms, with or without
		modification, are permitted provided that the following conditions are met:
			* Redistributions of source code must retain the above copyright
			  notice, this list of conditions and the following disclaimer.
			* Redistributions in binary form must reproduce the above copyright
			  notice, this list of conditions and the following disclaimer in the
			  documentation and/or other materials provided with the distribution.
			* Neither the name of the <organization> nor the
			  names of its contributors may be used to endorse or promote products
			  derived from this software without specific prior written permission.

		THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
		ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
		WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
		DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
		DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
		(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
		LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
		ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
		(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
		SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	</License>
	<Owner Name="Daniel Vaughan" Email="danielvaughan@outcoder.com" />
	<CreationDate>2010-08-18 17:44:10Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Channels;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;

using Coding4Fun.Toolkit.Controls;

using Microsoft.Phone.Controls;

using Outcoder.Services.DialogService;

using InputPrompt = Outcoder.UI.Xaml.Controls.InputPrompt;
using PopUpResult = Coding4Fun.Toolkit.Controls.PopUpResult;
using OutcoderPopUpResult = Outcoder.UI.Xaml.Controls.PopUpResult;

namespace Outcoder.Services.Implementation
{
	public partial class DialogService : DialogServiceBase
	{
		//MessagePrompt messagePrompt;
		readonly int millisecondsToAllowAppBarToClose = 150;
		
		MessagePrompt CreateOrRetrieveMessagePrompt()
		{
			return new MessagePrompt(); //messagePrompt ?? (messagePrompt = new MessagePrompt());
		}

		public bool WaitForSystemTrayToClose { get; set; }

		public override async Task<int?> ShowDialogAsync(
			object body, IEnumerable<object> buttons, string caption = null,
			int defaultAcceptButtonIndex = -1,
			object details = null,
			DialogController dialogController = null)
		{
			await GiveTimeForAppBarToClose();

			TaskCompletionSource<int?> resultSource = new TaskCompletionSource<int?>();

			MessagePrompt messagePrompt = CreateOrRetrieveMessagePrompt();
			messagePrompt.Title = caption;
			string bodyText = body as string;
			if (bodyText != null)
			{
				var textBlock = new TextBlock
									{
										Text = bodyText,
										TextWrapping = TextWrapping.Wrap
									};
				messagePrompt.Body = textBlock;
			}
			else
			{
				messagePrompt.Body = body;
			}
			
			messagePrompt.ActionPopUpButtons.Clear();
			int? tappedButtonIndex = null;

			if (buttons != null)
			{
				int count = -1;

				foreach (var button in buttons)
				{
					count++;

					Button buttonToAdd = button as Button;
					if (buttonToAdd == null)
					{
						buttonToAdd = new Button { Content = button };
					}

					int buttonIndex = count;
					buttonToAdd.Tap += delegate
					{
						tappedButtonIndex = buttonIndex;
						messagePrompt.Hide();
					};

					messagePrompt.ActionPopUpButtons.Add(buttonToAdd);
				}
			}

			EventHandler closeRequestedHandler = null;

			if (dialogController != null)
			{
				closeRequestedHandler = (sender, args) =>
					{
						dialogController.CloseRequested -= closeRequestedHandler;
						messagePrompt.Hide();
					};

				dialogController.CloseRequested += closeRequestedHandler;
			}

			EventHandler<PopUpEventArgs<string, PopUpResult>> completedHandler = null;

			completedHandler = (sender, args) =>
				{
					if (dialogController != null && closeRequestedHandler != null)
					{
						dialogController.CloseRequested -= closeRequestedHandler;
					}

					messagePrompt.Completed -= completedHandler;
					resultSource.SetResult(tappedButtonIndex);
				};
			messagePrompt.Completed += completedHandler;

			try
			{
				Interlocked.Increment(ref openDialogCount);
				messagePrompt.Show();

				var result = await resultSource.Task;
				return result;
			}
			catch
			{
				messagePrompt.Completed -= completedHandler;

				throw;
			}
			finally
			{
				Interlocked.Decrement(ref openDialogCount);
			}			
		}

		/// <summary>
		/// If you prefer the Okay button on the right when calling ShowDialogAsync,
		/// set this property to <c>true</c>.
		/// </summary>
		public bool PlaceOkayButtonToTheRightOfCancel { get; set; }

		public override async Task<DialogResult> ShowDialogAsync(
			object content, 
			string caption, 
			DialogButton dialogButton,
			DialogImage dialogImage = DialogImage.None, 
			DialogImportance importanceThreshold = DialogImportance.High, 
			object details = null,
			DialogController dialogController = null)
		{
			await GiveTimeForAppBarToClose();

			MessagePrompt messagePrompt = CreateOrRetrieveMessagePrompt();

			if (PlaceOkayButtonToTheRightOfCancel)
			{
				/* Hack to place okay button on the right. */
				var buttons = messagePrompt.ActionPopUpButtons;
				if (buttons.Count > 1)
				{
					var okButton = buttons.First();
					buttons.Remove(okButton);
					buttons.Add(okButton);
				}
			}

			messagePrompt.Title = caption;

			string bodyText = content as string;
			if (bodyText != null)
			{
				var textBlock = new TextBlock
				{
					Text = bodyText,
					TextWrapping = TextWrapping.Wrap
				};
				messagePrompt.Body = textBlock;
			}
			else
			{
				messagePrompt.Body = content;
			}

			switch (dialogButton)
			{
				case DialogButton.OK:
					messagePrompt.IsCancelVisible = false;
					break;
				case DialogButton.OKCancel:
					messagePrompt.IsCancelVisible = true;
					break;
				case DialogButton.YesNo:
					/* The cancel button, represented by a cross, is used as the 'no' button. */
					messagePrompt.IsCancelVisible = true;
					break;
				case DialogButton.YesNoCancel:
					/* This is not properly implemented. 
					 * Should show three buttons, but requires localization. */
					messagePrompt.IsCancelVisible = true;
					break;
				default:
					throw new Exception("Unknown DialogButton: " + dialogButton);
			}
			
			EventHandler closeRequestedHandler = null;

			if (dialogController != null)
			{
				closeRequestedHandler = (sender, args) =>
				{
					dialogController.CloseRequested -= closeRequestedHandler;
					messagePrompt.Hide();
				};

				dialogController.CloseRequested += closeRequestedHandler;
			}

			var resultSource = new TaskCompletionSource<DialogResult>();
			bool completedRaised = false;

			EventHandler<PopUpEventArgs<string, PopUpResult>> completedHandler = null;
			completedHandler = delegate(object sender, PopUpEventArgs<string, PopUpResult> args)
				{
					try
					{
						if (dialogController != null && closeRequestedHandler != null)
						{
							dialogController.CloseRequested -= closeRequestedHandler;
						}

						messagePrompt.Completed -= completedHandler;

						if (completedRaised)
						{
							return;
						}
						completedRaised = true;
					
						DialogResult tapResult;
						PopUpResult popUpResult = args.PopUpResult;

						if (popUpResult == PopUpResult.Cancelled)
						{
							/* If it's a Yes/No dialog then a cancel indicates that the 'no' (cross) was tapped. */
							if (dialogButton == DialogButton.YesNo)
							{
								tapResult = DialogResult.No;
							}
							else
							{
								tapResult = DialogResult.Cancel;
							}
						}
						else if (popUpResult == PopUpResult.NoResponse)
						{
							tapResult = DialogResult.None;
						}
						else if (popUpResult == PopUpResult.UserDismissed)
						{
							tapResult = DialogResult.None;
						}
						else if (popUpResult == PopUpResult.Ok)
						{
							if (dialogButton == DialogButton.YesNo
							    || dialogButton == DialogButton.YesNoCancel)
							{
								tapResult = DialogResult.Yes;
							}
							else
							{
								tapResult = DialogResult.OK;
							}
						}
						else
						{
							throw new Exception("DialogResult was unable to be determined.");
						}

						resultSource.SetResult(tapResult);
					}
					catch (Exception ex)
					{
						resultSource.SetException(ex);
					}
				};

			

			try
			{
				messagePrompt.Completed += completedHandler;

				Interlocked.Increment(ref openDialogCount);

				messagePrompt.Show();
			}
			catch (Exception ex)
			{
				Interlocked.Decrement(ref openDialogCount);
				messagePrompt.Completed -= completedHandler;
				throw;
			}

			DialogResult result;
			try
			{
				result = await resultSource.Task;
			}
			finally
			{
				Interlocked.Decrement(ref openDialogCount);
			}

			return result;
		}

		/// <summary>
		/// This method wait for a moment to allow the AppBar to close. 
		/// The reason is that toasts and dialogs get there why offset according to whether the system tray is open. 
		/// In some apps, the ApplicationBar's menu expansion. I.e., if the app bar is open then the system tray is shown. 
		/// When the app bar is closed the system tray is hidden. 
		/// When a story board is used for the transition we need to allow it to start.
		/// </summary>
		/// <returns></returns>
		async Task GiveTimeForAppBarToClose()
		{
			if (WaitForSystemTrayToClose)
			{
				/* Wait for AppBar to close because if the system tray's visibility then the dialog will be off. */
				await Task.Delay(TimeSpan.FromMilliseconds(millisecondsToAllowAppBarToClose));
				//Thread.Sleep(millisecondsToAllowAppBarToClose);
			}
		}

		public override async Task<QuestionResponse<TResponse>> AskQuestionAsync<TResponse>(
			IQuestion<TResponse> question)
		{
			ArgumentValidator.AssertNotNull(question, "body");

			await GiveTimeForAppBarToClose();

			var textResponseQuestion = question as TextResponseQuestion;
			if (textResponseQuestion != null)
			{
				var textBlock = new TextBlock
				{
					Text = textResponseQuestion.Question,
					TextWrapping = TextWrapping.Wrap
				};

				InputScope inputScope = new InputScope();
				inputScope.Names.Add(new InputScopeName { NameValue = textResponseQuestion.InputScope });

				var textBox = new TextBox
					{
						InputScope = inputScope, 
						Text = textResponseQuestion.DefaultResponse ?? string.Empty,
						Margin = new Thickness(-12,12,-12,12)
					};
				//textBox.Loaded += HandleTextBoxLoaded;
				//textBox.KeyUp += (sender, args) => { };
				
				StackPanel panel = new StackPanel();
				panel.Children.Add(textBlock);
				panel.Children.Add(textBox);

				var showDialogResult = await ShowDialogAsync(panel, textResponseQuestion.Caption, DialogButton.OKCancel);
				var okCancelQuestionResult = showDialogResult == DialogResult.OK || showDialogResult == DialogResult.Yes 
											? OkCancelQuestionResult.Ok : OkCancelQuestionResult.Cancel;
				TextResponse textResponse = new TextResponse(okCancelQuestionResult, textBox.Text);
				//var response = new QuestionResponse<TextResponse>(textResponse, textResponseQuestion);
				var result = new QuestionResponse<TResponse>((TResponse)(object)textResponse, question);
				return result;
			}

			var twoButtonQuestion = question as TwoButtonQuestion;
			if (twoButtonQuestion == null)
			{
				throw new NotImplementedException("Currently this async method only supports TwoButtonQuestion questions.");
			}

			string caption = twoButtonQuestion.Caption != null ? twoButtonQuestion.Caption.ToString() : null;
			string message = twoButtonQuestion.Body != null ? twoButtonQuestion.Body.ToString() : null;

			CustomMessageBox messageBox = new CustomMessageBox()
				{
					LeftButtonContent = twoButtonQuestion.LeftButtonContent,
					RightButtonContent = twoButtonQuestion.RightButtonContent,
					Caption = caption,
					Message = message
				};

			var source = new TaskCompletionSource<QuestionResponse<TResponse>>();

			messageBox.Dismissed += (sender, args) =>
				{
					TwoButtonResponseValue responseValue;
					
					switch (args.Result)
					{
						case CustomMessageBoxResult.LeftButton:
							responseValue = TwoButtonResponseValue.LeftButton;
							break;
						case CustomMessageBoxResult.RightButton:
							responseValue = TwoButtonResponseValue.RightButton;
							break;
						default:
							responseValue = TwoButtonResponseValue.None;
							break;
					}

					//TwoButtonResponse response = new TwoButtonResponse(responseValue);
					var result = new QuestionResponse<TResponse>((TResponse)(object)responseValue, question);
					try
					{
						source.SetResult(result);
					}
					catch (InvalidOperationException)
					{
						/* When deactivation occurrs, an exception 
						 * may be raised inexplicably on reactivation - ignore. */
					}
				};

			try
			{
				Interlocked.Increment(ref openDialogCount);

				messageBox.Show();
			}
			catch (Exception ex)
			{
				Interlocked.Decrement(ref openDialogCount);
				throw;
			}

			try
			{
				return await source.Task;
			}
			finally
			{
				Interlocked.Decrement(ref openDialogCount);
			}
			
		}

		void HandleTextBoxLoaded(object sender, RoutedEventArgs e)
		{
			var textBox = (Control)sender;
			textBox.Focus();
			textBox.Loaded -= HandleTextBoxLoaded;
		}

		void AskQuestionWithTextResponse(
			TextResponseQuestion textResponseQuestion,
			Action<QuestionResponse<TextResponse>> resultHandler)
		{
			try
			{
				AskQuestionWithTextResponseCore(textResponseQuestion, resultHandler);
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex);
				Deployment.Current.Dispatcher.BeginInvoke(
					delegate
						{
							resultHandler(new QuestionResponse<TextResponse>(null, textResponseQuestion, ex));
						});
			}
		}

		void AskQuestionWithTextResponseCore(
			TextResponseQuestion textResponseQuestion,
			Action<QuestionResponse<TextResponse>> resultHandler)
		{
			OkCancelQuestionResult questionResult;

			Deployment.Current.Dispatcher.InvokeIfRequired(
				delegate
			        {
			        	try
			        	{
							InputScope inputScope = new InputScope();
							inputScope.Names.Add(new InputScopeName { NameValue = textResponseQuestion.InputScope });

							InputPrompt prompt = new InputPrompt
							                     	{
							                     		Title = textResponseQuestion.Caption, 
														Message = textResponseQuestion.Question,
														Value = textResponseQuestion.DefaultResponse,
														InputScope = inputScope
							                     	};

			        		bool[] callThrewException = {false};

							prompt.Completed += (sender, args) =>
							                    	{
							                    		if (!callThrewException[0])
							                    		{
							                    			Interlocked.Decrement(ref openDialogCount);
							                    		}

														if (args.Error != null)
														{
															resultHandler(new QuestionResponse<TextResponse>(null, textResponseQuestion, args.Error));
															return;
														}

														questionResult = args.PopUpResult == OutcoderPopUpResult.OK 
															? OkCancelQuestionResult.Ok 
															: OkCancelQuestionResult.Cancel;

														var textResponse = new TextResponse(questionResult, args.Result);
														var result = new QuestionResponse<TextResponse>(textResponse, textResponseQuestion);
														resultHandler(result);
							                    	};
			        		try
			        		{
								Interlocked.Increment(ref openDialogCount);
								prompt.Show();
			        		}
			        		catch (Exception)
			        		{
			        			callThrewException[0] = true;
			        			Interlocked.Decrement(ref openDialogCount);
			        		}
			        	}
			        	catch (Exception ex)
			        	{
			        		Console.WriteLine(ex);
							resultHandler(new QuestionResponse<TextResponse>(null, textResponseQuestion, ex));
			        	}
			        });

		}

		public override async Task<object> ShowToastAsync(ToastParameters toastParameters)
		{
			ArgumentValidator.AssertNotNull(toastParameters, "toastParameters");

			await GiveTimeForAppBarToClose();

			ToastPrompt toast = new ToastPrompt();

			var body = toastParameters.Body;
			if (body != null)
			{
				/* Hopefully we can open this up to an object later. */
				string bodyText = body.ToString();
				if (!string.IsNullOrWhiteSpace(bodyText))
				{
					toast.Message = bodyText;
					toast.TextWrapping = TextWrapping.Wrap;
				}
			}

			var caption = toastParameters.Caption;
			if (caption != null)
			{
				/* Hopefully we can open this up to an object later. */
				var captionText = caption.ToString();
				if (!string.IsNullOrWhiteSpace(captionText))
				{
					toast.Title = captionText;
				}
			}

			ToastTextOrientation? textOrientation = toastParameters.TextOrientation;
			if (textOrientation.HasValue)
			{
				ToastTextOrientation orientation = textOrientation.Value;
				toast.TextOrientation = orientation == ToastTextOrientation.Horizontal
											? Orientation.Horizontal
											: Orientation.Vertical;
			}
			//else if (string.IsNullOrWhiteSpace(toastParameters.Caption) ||
			//	string.IsNullOrWhiteSpace(toastParameters.Message))
			//{
			//	toast.TextOrientation = Orientation.Horizontal;
			//}

			if (toastParameters.MillisecondsUntilHidden.HasValue)
			{
				toast.MillisecondsUntilHidden = toastParameters.MillisecondsUntilHidden.Value;
			}

			Uri imageUri = toastParameters.ImageUri;
			if (imageUri != null)
			{
				toast.ImageSource = new BitmapImage(imageUri);
			}

			double? imageWidth = toastParameters.ImageWidth;
			if (imageWidth.HasValue)
			{
				toast.ImageWidth = imageWidth.Value;
			}

			double? imageHeight = toastParameters.ImageHeight;
			if (imageHeight.HasValue)
			{
				toast.ImageWidth = imageHeight.Value;
			}

			if (toastParameters.VerticalOrientation.HasValue)
			{
				switch (toastParameters.VerticalOrientation.Value)
				{
					case ToastVerticalOrientation.Top:
						toast.VerticalAlignment = VerticalAlignment.Top;
						break;
					case ToastVerticalOrientation.Center:
						toast.VerticalAlignment = VerticalAlignment.Center;
						break;
					case ToastVerticalOrientation.Bottom:
						toast.VerticalAlignment = VerticalAlignment.Bottom;
						var appBar = Page.ApplicationBar;
						if (appBar.IsVisible && appBar.Opacity < 1)
						{
							toast.Margin = new Thickness(0,0,0,72);
						}
						break;
					case ToastVerticalOrientation.Stretch:
						toast.VerticalAlignment = VerticalAlignment.Stretch;
						break;
				}
			}
			
			TaskCompletionSource<object> source = new TaskCompletionSource<object>();
			EventHandler<PopUpEventArgs<string, PopUpResult>> completedHandler = null;
			
			completedHandler = (o, e) =>
				{
					toast.Completed -= completedHandler;

					Exception error = e.Error;
					if (error != null)
					{
						source.SetException(error);
					}
					else
					{
						source.SetResult(null);
					}
				};
			
			toast.Completed += completedHandler;
			toast.Show();

			return await source.Task;
		}

		internal PhoneApplicationPage Page
		{
			get 
			{ 
				return RootFrame.GetFirstLogicalChildByType<PhoneApplicationPage>(false);
			}
		}

		static Frame RootFrame
		{
			get
			{
#if WINDOWS_STORE
				var rootFrame = Window.Current.Content as Frame;
#elif WINDOWS_PHONE
				var rootFrame = Application.Current.RootVisual as Frame;
#endif

				return rootFrame;
			}

		}
	}

//	public class TextResponseViewModel
//	{
//		public TextResponseQuestion TextResponseQuestion { get; private set; }
//		public string Response { get; set; }
//		public string Caption { get; private set; }
//		public string Question { get; private set; }
//
//		public TextResponseViewModel(TextResponseQuestion textResponseQuestion)
//		{
//			TextResponseQuestion = textResponseQuestion;
//			Caption = string.IsNullOrEmpty(textResponseQuestion.Caption) 
//				? "Question" : textResponseQuestion.Caption;
//			Question = textResponseQuestion.Question;
//		}
//	}
}
